package com.habdqn.studentmanager.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;
import javax.transaction.Transactional;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.xiaofan1519.verify.exception.VerifyException;
import com.habdqn.studentmanager.bean.AttendanceMachine;
import com.habdqn.studentmanager.bean.AttendanceRecord;
import com.habdqn.studentmanager.bean.AttendanceSetting;
import com.habdqn.studentmanager.bean.Classes;
import com.habdqn.studentmanager.bean.Student;
import com.habdqn.studentmanager.bean.StudentAttRecord;
import com.habdqn.studentmanager.bean.Teacher;
import com.habdqn.studentmanager.bean.ajax.Page;
import com.habdqn.studentmanager.constant.ConnState;
import com.habdqn.studentmanager.constant.LogType;
import com.habdqn.studentmanager.controller.AttendanceController;
import com.habdqn.studentmanager.exception.StudentException;
import com.habdqn.studentmanager.mapper.AttendanceMachineMapper;
import com.habdqn.studentmanager.mapper.AttendanceRecordMapper;
import com.habdqn.studentmanager.mapper.AttendanceSettingMapper;
import com.habdqn.studentmanager.util.AttendanceConnection;
import com.habdqn.studentmanager.util.AttendanceConnectionManager;
import com.habdqn.studentmanager.util.ConfigUtil;
import com.habdqn.studentmanager.util.DateUtil;
import com.habdqn.studentmanager.util.ExcelUtil;
import com.habdqn.studentmanager.util.ExcelUtil.Excel.Row;
import com.habdqn.studentmanager.util.LocalTimeUtil;
import com.habdqn.studentmanager.util.ProjectUtil;
import com.habdqn.studentmanager.util.SystemLogger;

/**
 * @author Fan 考勤服务类 2017年7月4日
 * 
 */
@Service
public class AttendanceService {

	private static final Logger logger = LoggerFactory.getLogger(AttendanceService.class);

	@Resource
	private AttendanceMachineMapper attMachineMapper;

	@Resource
	private AttendanceRecordMapper attRecordMapper;

	@Resource
	private AttendanceSettingMapper attSettingMapper;

	@Resource
	private StudentService studentService;

	@Resource
	private SystemLogger sysLogger;

	/**
	 * 根据id获取考勤机信息
	 * 
	 * @param bean
	 * @return
	 */
	public AttendanceMachine getMachine(AttendanceMachine bean) {

		try {
			AttendanceMachine machine = attMachineMapper.find(bean);
			return machine;
		}
		catch (RuntimeException e) {
			logger.error("查询考勤机失败", e);
			throw new StudentException("查询考勤机失败", e);
		}
	}

	/**
	 * 查询考勤机列表
	 * 
	 * @param bean
	 * @param pageNum
	 * @param pageSize
	 * @return
	 */
	public PageInfo<AttendanceMachine> findMachineList(AttendanceMachine bean, Page page) {

		List<AttendanceMachine> lists = null;

		try {
			if (null != page) {
				PageHelper.startPage(page.getPageNum(), page.getPageSize());
			}

			lists = attMachineMapper.findList(bean);

			// 区分是否是内部调用
			if (null == page) {
				return new PageInfo<AttendanceMachine>(lists);
			}
			
			// 获取考勤机连接状态
			for (AttendanceMachine machine : lists) {
				AttendanceConnection conn = AttendanceConnectionManager.get(machine);
				if (null != conn) {
					machine.setStatus(conn.getStatus());
				}
				else {
					machine.setStatus(ConnState.DEFAULT.toInt());
				}
			}
		}
		catch (RuntimeException e) {
			logger.error("查询考勤机列表失败", e);
			throw new StudentException("查询考勤机列表失败", e);
		}

		return new PageInfo<AttendanceMachine>(lists);
	}

	/**
	 * 新增
	 * 
	 * @param bean
	 */
	public void addMachine(AttendanceMachine bean) {
		try {
			attMachineMapper.insert(bean);
		}
		catch (DuplicateKeyException e) {
			logger.debug("duplicateKey: {}", bean.getId(), e);
			throw new StudentException("名称或地址已存在, 请重新输入", e);
		}
		catch (RuntimeException e) {
			logger.error("添加考勤机失败", e);
			throw new StudentException("添加考勤机失败", e);
		}
		sysLogger.record(LogType.AddType, "新增考勤机:" + bean.getName());
		AttendanceConnectionManager.add(bean);
	}

	/**
	 * 删除
	 * 
	 * @param bean
	 */
	public void deleteMachine(AttendanceMachine bean) {
		int num = -1;
		try {
			num = attMachineMapper.delete(bean);
		}
		catch (RuntimeException e) {
			logger.error("删除考勤机失败", e);
			throw new StudentException("删除考勤机失败", e);
		}
		if (num != -1) {
			logger.debug("删除考勤机,编号:{}", bean.getId());
			sysLogger.record(LogType.DelType, "删除考勤机,编号:" + bean.getId());
			AttendanceConnectionManager.remove(bean.getId());
		}
		else {
			logger.debug("删除考勤机失败，没有找到id为：{} 的考勤机", bean.getId());
		}
	}

	/**
	 * 更新
	 * 
	 * @param bean
	 */
	public void updateMachine(AttendanceMachine bean) {
		int num = -1;
		try {
			num = attMachineMapper.update(bean);
		}
		catch (Exception e) {
			logger.error("更新考勤机失败", e);
			throw new StudentException("更新考勤机失败", e);
		}
		if (num != -1) {
			logger.debug("更新考勤机, 编号：{}", bean.getId());
			AttendanceConnectionManager.remove(bean.getId());
			AttendanceConnectionManager.add(bean);
			sysLogger.record(LogType.UpdateType, "更新考勤机:" + bean.getName());
		}
		else {
			logger.error("没有找到对应的考勤机, 编号：{}", bean.getId());
		}
	}

	/**
	 * 增加考勤记录
	 * 
	 * @param bean
	 * @param isMachine 是否是考勤机录入
	 */
	public void addRecord(AttendanceRecord bean, boolean isMachine) {

		Student student = studentService.find(bean.getStudent());
		bean.setStudent(student);
		bean.setCreateDate(new Date());
		
		// 判断是否考勤机签到，还是补签到
		Teacher current = null;
		if (isMachine) {
			current = new Teacher();
			current.setId(1);
		}
		else {
			current = ProjectUtil.getCurrentUser();
			bean.setTeacher(current);
			// 如果是补签到，且备注为空，默认塞补签到。
			if(StringUtils.isEmpty(bean.getExt1())) {
				bean.setExt1("补签到");
			}
		}
		
		try {
			attRecordMapper.insert(bean);
		}
		catch (RuntimeException e) {
			logger.error("添加考勤失败", e);
			throw new StudentException("添加考勤失败", e);
		}
		
		sysLogger.record(current, LogType.AddType, "新增考勤记录, 学生学号:" + student.getId() + ", 考勤时间:"
		        + DateUtil.format(bean.getAttDate(), DateUtil.FormatStr19));
	}

	/**
	 * 查询考勤详细记录
	 * @param bean
	 * @param pageNum
	 * @param pageSize
	 * @return
	 */
	public PageInfo<AttendanceRecord> findRecordList(AttendanceRecord bean, Integer pageNum, Integer pageSize) {
		
		try {
			if (null != pageNum && null != pageSize) {
				PageHelper.startPage(pageNum, pageSize);
			}	
			return new PageInfo<>(attRecordMapper.findList(bean));
		}
		catch (RuntimeException e) {
			logger.error("查询考勤详细记录失败", e);
			throw new StudentException("查询考勤详细记录失败", e);
		}
	}
	
	/**
	 * 查询学生考勤记录
	 * 
	 * @param bean
	 * @param pageNum
	 * @param pageSize
	 * @return
	 */
	public PageInfo<StudentAttRecord> findStudentRecordList(StudentAttRecord bean, Integer pageNum, Integer pageSize) {

		AttendanceSetting setting = getSetting();
		LocalTime onWork = setting.getOnWork();
		LocalTime offWork = setting.getOffWork();
		// 加上允许迟到时间
		onWork = onWork.plusMinutes(setting.getAllowLate());
		// 减去允许早退时间
		offWork = offWork.minusMinutes(setting.getAllowLeave());

		if (null != pageNum && null != pageSize) {
			PageHelper.startPage(pageNum, pageSize);
		}
		List<StudentAttRecord> studentRecords = null;
		
		try {
			studentRecords = attRecordMapper.findListByStudent(bean);			
		}
		catch (RuntimeException e) {
			logger.error("查询学生考勤记录失败", e);
			throw new StudentException("查询学生考勤记录失败", e);
		}

		AttendanceRecord condition = new AttendanceRecord();
		// 设置日期条件
		condition.setAttDate(bean.getAttDate());

		for (int i = 0; i < studentRecords.size(); i++) {

			StudentAttRecord dbRecord = studentRecords.get(i);
			StudentAttRecord record = new StudentAttRecord();
			record.setAmState(0);
			record.setPmState(0);
			record.setStudent(dbRecord.getStudent());

			// 设置学生条件
			Student stu = new Student();
			stu.setInnerId(dbRecord.getStudent().getInnerId());
			condition.setStudent(stu);
			List<AttendanceRecord> atts = attRecordMapper.findList(condition);

			for (AttendanceRecord att : atts) {
				Date attDate = att.getAttDate();

				// 没有考勤记录
				if (null == attDate) {
					continue;
				}

				LocalTime attTime = LocalTimeUtil.getTime(attDate);

				// 如果有记录小于等于上班时间
				if (onWork.compareTo(attTime) >= 0) {
					record.setAmState(record.getAmState() + 1);
				}

				// 判断是否有迟到记录, 签到时间大于上班时间
				if (record.getAmState() == 0 && LocalTime.NOON.compareTo(attTime) >= 0
				        && onWork.compareTo(attTime) < 0) {
					record.setAmState(record.getAmState() - 1);
				}

				/*
				 * 判断早退记录 签到时间大于12点, 且下班时间 大于签到时间
				 */
				if (LocalTime.NOON.compareTo(attTime) < 0 && offWork.compareTo(attTime) > 0) {
					record.setPmState(record.getPmState() + 1);
					// 不需要统计后续
					break;
				}

				/*
				 * 下班后的签到记录 下班时间小于等于签到时间
				 */
				if (offWork.compareTo(attTime) <= 0) {
					record.setPmState(record.getPmState() - 1);
				}
			}

			record.setRecords(atts);
			studentRecords.set(i, record);
		}

		return new PageInfo<>(studentRecords);
	}

	/**
	 * 获取配置
	 * 
	 * @return
	 */
	public AttendanceSetting getSetting() {
		try {
			return attSettingMapper.get();
		}
		catch (RuntimeException e) {
			logger.error("查询考勤配置失败", e);
			throw new StudentException("查询考勤配置失败", e);
		}

	}

	/**
	 * 更新配置
	 * 
	 * @param bean
	 */
	public void updateSetting(AttendanceSetting bean) {
		int num = -1;
		try {
			bean.setUpdateDate(new Date());
			num = attSettingMapper.update(bean);
		}
		catch (RuntimeException e) {
			logger.error("更新排班设置失败", e);
			throw new StudentException("更新排班设置失败", e);
		}
		
		if (num != 1) {
			logger.error("更新失败, 没有对应的排班设置");
			throw new StudentException("更新失败, 没有对应的排班设置");
		}
		
		logger.debug("成功更新排班设置");
		sysLogger.record(LogType.UpdateType, "更新排班设置");
	}

	/**
	 * 批量导入考勤记录
	 * 
	 * @param is
	 */
	@Transactional
	public void importRecord(InputStream is) {

		ExcelUtil.init(is).sheetAt(0).rowAt(2).accept((Row row) -> {
			// 从1开始的行号
			int rowNum = row.getRowAt() + 1;

			try {
				accept(row);
			}
			catch (RuntimeException e) {
				logger.debug("导入失败", e);
				throw new StudentException("第" + rowNum + "行:" + e.getMessage(), e);
			}
		}).close();
	}

	/**
	 * 处理导入的数据
	 * 
	 * @param row
	 */
	private void accept(Row row) {

		AttendanceRecord record = new AttendanceRecord();
		Student student = new Student();
		String studentId = row.getCell(0).getValue();
		student.setId(studentId);
		student = studentService.find(student);
		record.setStudent(student);

		String attDateStr = row.getCell(1).getValue();
		Date attDate = DateUtil.parseDate(attDateStr, DateUtil.FormatStr19);
		if (null == attDate) {
			String msg = "考勤时间为空";
			if (StringUtils.isNotEmpty(attDateStr)) {
				msg = "考勤时间格式非法 格式参考:2017-07-12 10:45:00";
			}
			throw new StudentException(msg);
		}
		record.setAttDate(attDate);

		String remark = row.getCell(2).getValue();
		record.setExt1(remark);

		try {
			AttendanceController.validate(record);
		}
		catch (VerifyException e) {
			throw new StudentException("参数非法." + e.getMessage(), e);
		}

		addRecord(record, false);
	}

	/**
	 * 导出考勤记录
	 * 
	 * @param bean
	 * @param out
	 */
	public void export(StudentAttRecord bean, OutputStream out) {
		
		// 获取模板文件的路径
		String path = ProjectUtil.getRealPath() + ConfigUtil.getKey("excelDemoPath") + File.separator
		        + "attendance-template.xlsx";
		try (InputStream is = new FileInputStream(path)) {
			
			// 获取学生考勤记录
			PageInfo<StudentAttRecord> page = findStudentRecordList(bean, null, null);
			
			AttendanceRecord record = new AttendanceRecord();
			record.setStudent(bean.getStudent());
			record.setAttDate(bean.getAttDate());
			// 获取详细考勤记录
			PageInfo<AttendanceRecord> records = findRecordList(record, null, null);
			
			// 获取排班设置
			AttendanceSetting setting = getSetting();
			List<AttendanceSetting> settings = new ArrayList<>();
			settings.add(setting);
			
			ExcelUtil.init(is).sheetAt(0).rowAt(2).export(page.getList(), (StudentAttRecord t, Row row) -> {
				
				// 考勤记录页输出
				Student stu = t.getStudent();
				Classes clazz = stu.getClasses();
				
				if (null != clazz) {
					row.createCell(0).setValue(clazz.getName());					
				}
				
				row.createCell(1).setValue(stu.getId());
				row.createCell(2).setValue(stu.getName());
				row.createCell(3).setValue(t.getAmStateStr());
				row.createCell(4).setValue(t.getPmStateStr());
				
			}).sheetAt(1).rowAt(2).export(records.getList(), (AttendanceRecord t, Row row) -> {
				
				// 详细考勤记录输出
				Student stu = t.getStudent();
				Classes clazz = stu.getClasses();
				
				if (null != clazz) {
					row.createCell(0).setValue(clazz.getName());
				}
				row.createCell(1).setValue(stu.getId());
				row.createCell(2).setValue(stu.getName());
				
				String attDateStr = DateUtil.format(t.getAttDate(), DateUtil.FormatStr19);
				row.createCell(3).setValue(attDateStr);
				String createDateStr = DateUtil.format(t.getCreateDate(), DateUtil.FormatStr19);
				row.createCell(4).setValue(createDateStr);
				row.createCell(5).setValue(t.getExt1());
				
			}).sheetAt(2).rowAt(2).export(settings, (AttendanceSetting t, Row row) -> {
				
				// 排班规则输出
				row.createCell(0).setValue(t.getOnWork().toString());
				row.createCell(1).setValue(t.getOffWork().toString());
				row.createCell(2).setValue(t.getAllowLate().toString());
				row.createCell(3).setValue(t.getAllowLeave().toString());
				
			}).export(out).close();
		}
		catch (RuntimeException e) {
			logger.error("导出失败", e);
			throw new StudentException("导出失败", e);
		}
		catch (FileNotFoundException e) {
			logger.error("模板文件不存在", e);
			throw new StudentException("模板文件不存在", e);
		}
		catch (IOException e) {
			logger.error("操作文件失败", e);
			throw new StudentException("操作文件失败", e);
		}
	}
}
